{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6dd69519-afc9-4065-ad12-8253c708f5af",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "pn.extension()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a796364a-f1cd-411a-b7fd-d4354794474e",
   "metadata": {},
   "source": [
    "A `Viewer` is a good abstraction for combining some business logic about your application with Panel components and then letting you use it **as if it was** a Panel component. It provides a useful abstraction extension of a simple `Parameterized` class. Note however that it is not actually a Panel component, i.e. it does not behave like a widget, layout or pane. If you want to build a Panel component in Python the `PyComponent` class is a better option."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9164b959-7dc5-4579-b65c-1b3827803ca0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import param\n",
    "\n",
    "from panel.viewable import Viewer\n",
    "\n",
    "\n",
    "class CounterButton(Viewer):\n",
    "\n",
    "    value = param.Integer()\n",
    "\n",
    "    def __init__(self, **params):\n",
    "        super().__init__()\n",
    "        self._layout = pn.widgets.Button(\n",
    "            name=self._button_name, on_click=self._on_click, **params\n",
    "        )\n",
    "\n",
    "    def _on_click(self, event):\n",
    "        self.value += 1\n",
    "\n",
    "    @param.depends(\"value\")\n",
    "    def _button_name(self):\n",
    "        return f\"count is {self.value}\"\n",
    "\n",
    "    def __panel__(self):\n",
    "        return self._layout\n",
    "\n",
    "CounterButton()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78fda29d-eedd-45e4-a02c-e0771ac6b182",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "If you are looking to create new components using JavaScript, check out [`JSComponent`](JSComponent.md), [`ReactComponent`](ReactComponent.md), or [`AnyWidgetComponent`](AnyWidgetComponent.md) instead.\n",
    ":::\n",
    "\n",
    "## API\n",
    "\n",
    "### Attributes\n",
    "\n",
    "None. The `Viewer` class does not have any special attributes. It is a simple `param.Parameterized` class with a few additional methods. This also means you will have to add or support parameters like `height`, `width`, `sizing_mode`, etc., yourself if needed.\n",
    "\n",
    "### Methods\n",
    "\n",
    "- **`__panel__`**: Must be implemented. Should return the Panel component or object to be displayed.\n",
    "- **`servable`**: This method serves the component using Panel's built-in server when running `panel serve ...`.\n",
    "- **`show`**: Displays the component in a new browser tab when running `python ...`.\n",
    "\n",
    "## Usage\n",
    "\n",
    "### Styling with CSS\n",
    "\n",
    "You can style the component by styling the component(s) returned by `__panel__` using their `styles` or `stylesheets` attributes.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68f36432-40e1-4098-a195-12474dbf4a83",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import param\n",
    "\n",
    "from panel.viewable import Viewer\n",
    "\n",
    "\n",
    "class StyledCounterButton(Viewer):\n",
    "\n",
    "    value = param.Integer()\n",
    "\n",
    "    _stylesheets = [\n",
    "        \"\"\"\n",
    "        :host(.solid) .bk-btn.bk-btn-default\n",
    "        {\n",
    "            background: #0072B5;\n",
    "            color: white;\n",
    "            border: none;\n",
    "            padding: 10px;\n",
    "            border-radius: 4px;\n",
    "        }\n",
    "        :host(.solid) .bk-btn.bk-btn-default:hover {\n",
    "            background: #4099da;\n",
    "        }\n",
    "        \"\"\"\n",
    "    ]\n",
    "\n",
    "    def __init__(self, **params):\n",
    "        super().__init__()\n",
    "\n",
    "        self._layout = pn.widgets.Button(\n",
    "            name=self._button_name,\n",
    "            on_click=self._on_click,\n",
    "            stylesheets=self._stylesheets,\n",
    "            **params,\n",
    "        )\n",
    "\n",
    "    def _on_click(self, event):\n",
    "        self.value += 1\n",
    "\n",
    "    @param.depends(\"value\")\n",
    "    def _button_name(self):\n",
    "        return f\"Clicked {self.value} times\"\n",
    "\n",
    "    def __panel__(self):\n",
    "        return self._layout\n",
    "\n",
    "\n",
    "StyledCounterButton().servable()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b134810-94f9-4ccf-a257-aa07cb48d6f3",
   "metadata": {},
   "source": [
    "See the [Apply CSS](../../how_to/styling/apply_css.md) guide for more information on styling Panel components.\n",
    "\n",
    "## Displaying A Single Child\n",
    "\n",
    "You can display Panel components (`Viewable`s) by defining a `Child` parameter.\n",
    "\n",
    "Let's start with the simplest example:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42f21642-ad17-4c21-b186-d894c90da554",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "from panel.custom import Child\n",
    "from panel.viewable import Viewer\n",
    "\n",
    "class SingleChild(Viewer):\n",
    "\n",
    "    object = Child()\n",
    "\n",
    "    def __panel__(self):\n",
    "      return pn.Column(\"A Single Child\", self.param.object.rx())\n",
    "\n",
    "single_child = SingleChild(object=pn.pane.Markdown(\"A **Markdown** pane!\"))\n",
    "\n",
    "single_child.servable()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ba02dcb-c83b-4212-ac1f-8e14e895d485",
   "metadata": {},
   "source": [
    "Calling `self.param.object.rx()` creates a reactive expression which updates when the `object` parameter is updated.\n",
    "\n",
    "Let's replace the `object` with a `Button`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e386a52-d256-47a9-9139-a0e81e7bad88",
   "metadata": {},
   "outputs": [],
   "source": [
    "single_child.object = pn.widgets.Button(name=\"Click me\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dbf41ea-b18f-4421-977a-291612cda6f7",
   "metadata": {},
   "source": [
    "Let's change it back"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "880176b1-5052-4da1-8fbb-cce9e39524ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "single_child.object = pn.pane.Markdown(\"A **Markdown** pane!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10cc810a-6001-49d3-a5e1-798252e96a5d",
   "metadata": {},
   "source": [
    "If you provide a non-`Viewable` child it will automatically be converted to a `Viewable` by `pn.panel`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d929e9b1-62da-4122-bc78-7129988de486",
   "metadata": {},
   "outputs": [],
   "source": [
    "SingleChild(object=\"A **Markdown** pane!\").servable()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "417c59c4-4e0d-46d1-a762-98af457034d5",
   "metadata": {},
   "source": [
    "If you want to allow a certain type of Panel components only, you can specify the specific type in the `class_` argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f02e8336-a3fa-4e58-b457-d71f5d84fd18",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "from panel.custom import Child\n",
    "from panel.viewable import Viewer\n",
    "\n",
    "class SingleChild(Viewer):\n",
    "\n",
    "    object = Child(class_=pn.pane.Markdown)\n",
    "\n",
    "    def __panel__(self):\n",
    "        return pn.Column(\"A Single Child\", self.param.object.rx())\n",
    "\n",
    "\n",
    "SingleChild(object=pn.pane.Markdown(\"A **Markdown** pane!\")).servable()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ba7de66-21a3-4ab8-8eb2-0e6fd7d488fd",
   "metadata": {},
   "source": [
    "The `class_` argument also supports a tuple of types:\n",
    "\n",
    "```python\n",
    "    object = Child(class_=(pn.pane.Markdown, pn.widgets.Button))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "000a0276-717b-4984-9d1e-e936ba8c1a05",
   "metadata": {},
   "source": [
    "## Displaying a List of Children\n",
    "\n",
    "You can also display a `List` of `Viewable` objects using the `Children` parameter type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4fa17c5-65d2-4c1a-a311-aad61a4bf433",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "from panel.custom import Children\n",
    "from panel.viewable import Viewer\n",
    "\n",
    "\n",
    "class MultipleChildren(Viewer):\n",
    "\n",
    "    objects = Children()\n",
    "\n",
    "    def __init__(self, **params):\n",
    "        super().__init__(**params)\n",
    "        self._layout = pn.Column(objects=self.param['objects'], styles={\"background\": \"silver\"})\n",
    "\n",
    "    def __panel__(self):\n",
    "        return self._layout\n",
    "\n",
    "\n",
    "MultipleChildren(\n",
    "    objects=[\n",
    "        pn.panel(\"A **Markdown** pane!\"),\n",
    "        pn.widgets.Button(name=\"Click me!\"),\n",
    "        {\"text\": \"I'm shown as a JSON Pane\"},\n",
    "    ]\n",
    ").servable()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60ebdbd0-48c8-4f21-9a57-7b49e31523ae",
   "metadata": {},
   "source": [
    ":::note\n",
    "You can change the `item_type` to a specific subtype of `Viewable` or a tuple of `Viewable` subtypes.\n",
    ":::\n",
    "\n",
    "## References\n",
    "\n",
    "### Tutorials\n",
    "\n",
    "- [Reusable Components](../../tutorials/intermediate/reusable_components.md)\n",
    "\n",
    "### How-To Guides\n",
    "\n",
    "- [Combine Existing Widgets](../../how_to/custom_components/custom_viewer.md)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
